package com.base.core.common.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.base.core.exception.BusinessException;
import com.base.core.exception.enums.UserErrorCode;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.Map;

/**
 * JWT（JSON Web Token）工具类
 * 提供JWT的生成、解析、验证和刷新功能
 * 基于Auth0的java-jwt库实现
 */
@Component // 声明为Spring组件，可被其他类自动注入
public class JwtUtils {

    // 从配置文件中注入JWT签名密钥（必须保密且足够复杂）
    @Value("${jwt.secret}")
    private String secret;

    // 从配置文件中注入JWT过期时间（毫秒）
    @Value("${jwt.expiration}")
    private long expiration;

    @Value("${jwt.refreshExpiration}")
    private long refreshexpiration;

    // 从配置文件中注入JWT签发者标识
    @Value("${jwt.issuer}")
    private String issuer;

    /**
     * 获取JWT签名算法实例
     * 使用HMAC256算法，需要提供密钥
     * @return Algorithm 签名算法实例
     */
    private Algorithm getAlgorithm() {
        return Algorithm.HMAC256(secret); // 使用HS256算法+密钥生成签名
    }

    /**
     * 生成JWT令牌
     * @param subject 令牌主题（通常为用户唯一标识）
     * @param claims 自定义声明（键值对形式存储的额外信息）
     * @return String 生成的JWT令牌字符串
     */
    public String generateToken(String subject, Map<String, Object> claims) {
        return JWT.create()
                .withSubject(subject)          // 设置主题
                .withIssuer(issuer)            // 设置签发者
                .withIssuedAt(new Date())      // 设置签发时间
                .withExpiresAt(new Date(System.currentTimeMillis() + expiration)) // 设置过期时间
                .withPayload(claims)           // 添加自定义声明（如用户角色等）
                .sign(getAlgorithm());         // 使用指定算法签名
    }
    public String generateRefreshToken(String subject, Map<String, Object> claims) {
        return JWT.create()
                .withSubject(subject)          // 设置主题
                .withIssuer(issuer)            // 设置签发者
                .withIssuedAt(new Date())      // 设置签发时间
                .withExpiresAt(new Date(System.currentTimeMillis() + expiration)) // 设置过期时间
                .withPayload(claims)           // 添加自定义声明（如用户角色等）
                .sign(getAlgorithm());         // 使用指定算法签名
    }

    /**
     * 解析JWT令牌（不验证签名）
     * @param token JWT令牌字符串
     * @return DecodedJWT 解码后的JWT对象
     * @warning 此方法仅解码，不验证签名有效性！
     */
    public DecodedJWT parseToken(String token) {
        return JWT.decode(token); // 直接解码令牌（适合已通过验证的令牌）
    }

    /**
     * 验证JWT令牌有效性
     * @param token JWT令牌字符串
     * @return boolean 是否验证通过
     * @throws RuntimeException 当验证失败时抛出异常
     */
    public boolean validateToken(String token) throws BusinessException {
        try {
            // 构建验证器：要求相同的算法和签发者
            JWTVerifier verifier = JWT.require(getAlgorithm())
                    .withIssuer(issuer)
                    .build();
            verifier.verify(token); // 验证签名和过期时间
            return true;
        } catch (JWTVerificationException e) {
            // 捕获以下异常：
            // - 签名无效（SignatureVerificationException）
            // - 令牌过期（TokenExpiredException）
            // - 声明无效（InvalidClaimException）等
            throw new BusinessException(UserErrorCode.JWT_TOKEN_EXPIRED);
        }
    }

    /**
     * 从JWT令牌中提取主题（Subject）
     * @param token JWT令牌字符串
     * @return String 主题内容（通常是用户ID）
     */
    public String getSubjectFromToken(String token) {
        return parseToken(token).getSubject(); // 解码后获取主题
    }

    /**
     * 刷新JWT令牌（更新过期时间，保留原有声明）
     * @param token 原始JWT令牌
     * @return String 刷新后的新令牌
     */
    public String refreshToken(String token) {
        DecodedJWT jwt = parseToken(token); // 解码原令牌
        return JWT.create()
                .withSubject(jwt.getSubject())  // 保留原主题
                .withIssuer(issuer)             // 相同签发者
                .withIssuedAt(new Date())       // 更新签发时间
                .withExpiresAt(new Date(System.currentTimeMillis() + expiration)) // 设置新过期时间
                .withPayload(jwt.getClaims())   // 复制所有声明
                .sign(getAlgorithm());          // 重新签名
    }
}